/*
 ============================================================================
 Name        : DerivedTypeEx.c
 Author      : mattia
 Version     :
 Copyright   : Your copyright notice
 Description : Compute Pi in MPI C++
 ============================================================================
 */

#include <stdio.h>
#include <openmpi/mpi.h>
#include <pthread.h>
#include <time.h>
#include <ctime>
#include <string.h>
#include "DppmParams.h"
#include "Utils.h"
#include "LoadBalancing.h"
#include "BestSolutionExchange.h"
#include "TuplesProcessing.h"
#include "Tuple.h"


#define MASTER 0
#define PRINTMASTER 1

int rank, size;
DppmParams * dppm;
SortedBuffer * tuples;
pthread_mutex_t tuples_mutex = PTHREAD_MUTEX_INITIALIZER;
Tuple * bestSolution;
pthread_mutex_t best_solution_mutex = PTHREAD_MUTEX_INITIALIZER;
bool end = false;
pthread_mutex_t	end_flag_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t idle_cond = PTHREAD_COND_INITIALIZER;

int main(int argc, char** argv){


	int numAct,	T;
	int* numPreds;
	int* numModes;
	int** Preds;
	int** ModesCosts;
	int** ModesTimes;


	MPI_Init(&argc, &argv);
//	int required;
//	MPI_Init_thread(&argc,&argv,MPI_THREAD_MULTIPLE,&required);
//	if(required < MPI_THREAD_MULTIPLE){
//		cout<<"ERROR WITH MPI_THREAD_MULTIPLE!!!!"<<endl;
//		MPI_Abort(MPI_COMM_WORLD,1);
//		MPI_Finalize();
//		exit(-1);
//	}

	MPI_Comm_rank(MPI_COMM_WORLD, &rank);
	MPI_Comm_size(MPI_COMM_WORLD, &size);


	if (!rank){

		if ((argc != 2) || readDppmSpec(argv[1],numAct,T,numPreds,Preds,numModes,ModesCosts,ModesTimes)){
				printf("Error!!\n");
				return -1;
		}
		debugPrinter(0,rank,"main",70,"Leido DppmParams info");
	}


	MPI_Bcast(&numAct,1,MPI_INT, MASTER, MPI_COMM_WORLD);
	MPI_Bcast(&T,1,MPI_INT, MASTER, MPI_COMM_WORLD);
	if(rank){
		numPreds = new int[numAct];
		numModes = new int[numAct];
		Preds = new int*[numAct];
		ModesCosts = new int*[numAct];
		ModesTimes = new int*[numAct];
	}
	MPI_Bcast(numPreds, numAct, MPI_INT, MASTER, MPI_COMM_WORLD);
	MPI_Bcast(numModes, numAct, MPI_INT, MASTER, MPI_COMM_WORLD);

	for(int i = 0; i < numAct; i++){
		if(rank){
			Preds[i] = new int[numPreds[i]];
			ModesCosts[i] = new int[numModes[i]];
			ModesTimes[i] = new int[numModes[i]];
		}
		MPI_Bcast(Preds[i], numPreds[i], MPI_INT, MASTER, MPI_COMM_WORLD);
		MPI_Bcast(ModesCosts[i], numModes[i], MPI_INT, MASTER, MPI_COMM_WORLD);
		MPI_Bcast(ModesTimes[i], numModes[i], MPI_INT, MASTER, MPI_COMM_WORLD);
	}

	//debugPrinter(0,rank,"main",97,"Antes de crear DppmParams info");

	dppm = new DppmParams(numAct,T,numPreds,Preds,numModes,ModesCosts,ModesTimes);

	//debugPrinter(0,rank,"main",101,"Creado DppmParams");

	Tuple::SetTuplesIntSize(numAct);

	bestSolution = new Tuple(numAct);

	tuples = new SortedBuffer();

	//inicializar tuples con los primeros prefijos
	if(!rank){
		int numModesAct0 = dppm->getActivityNumModes(0);
		int * costsAct0 = dppm->getActivityModesCosts(0);
		int * timesAct0 = dppm->getActivityModesTimes(0);
		Tuple * tuple;

		for(int i = 0 ; i < numModesAct0; i++){
			tuple = new Tuple(numAct);

			tuple->lastAct(0);
			tuple->time(timesAct0[i]);
			tuple->cost(costsAct0[i]);
			tuple->times(0,timesAct0[i]);
			tuple->modes(0,i);

			tuples->push(tuple);
		}
	}


	/*if(!rank){
		cout << "\n\n--------------------------------------------------\n\n";
		dppm->Print();
	}*/

	pthread_t loadBalancingThread = createThread(&LoadBalancing);
	//pthread_t bestSolutionExchangeThread = createThread(&BestSolutionExchange);
	/*************************/
	time_t tic = time(0);
	string aux("INICIO PROCESSING!  At ");
	aux.append(ctime(&tic));
	debugPrinter(0,rank,"main",198,aux.substr(0,aux.length()-1).c_str());
	/*************************/
	pthread_t tuplesProcessingThread = createThread(&TuplesProcessing);

    pthread_join(loadBalancingThread, NULL);
    //pthread_join(bestSolutionExchangeThread, NULL);
    pthread_join(tuplesProcessingThread, NULL);
    /*************************/
	time_t toc = time(0);
	aux = string("FIN PROCESSING!  At ");
	aux.append(ctime(&toc));
	int timeElapsed  = toc - tic;
	debugPrinter(0,rank,"main",198,aux.substr(0,aux.length()-1).c_str(),"Tiempo utilizado (en segundos)",timeElapsed);
	/*************************/

    int recvBuff[size][Tuple::tuplesIntSize];

    //Compartir soluciones con un Gather al root
    MPI_Gather(bestSolution->getArray(),Tuple::tuplesIntSize,MPI_INT,
    				recvBuff,Tuple::tuplesIntSize,MPI_INT,0,MPI_COMM_WORLD);

    if (!rank){

    	//Decido la mejor solucion
    	int BSIndex = 0;
    	int BSCost = bestSolution->cost();

		for(int i = 1; i < size; i++){
			if (Tuple::GetCostFromTupleArray(recvBuff[i]) < BSCost){
				BSCost = Tuple::GetCostFromTupleArray(recvBuff[i]);
				BSIndex = i;
			}
		}

		debugPrinter(0,rank,"main",176,"La mejor solucion la encontró el proceso:","BSIndex",BSIndex,"BSCost",BSCost);

		if(BSIndex != rank){
			delete bestSolution;
			bestSolution = new Tuple(Tuple::tuplesIntSize,recvBuff[BSIndex]);
		}

		printf("\n\nLa mejor solución es: \n");
		bestSolution->print();

    }


	MPI_Finalize();

	delete dppm;
	delete tuples;
	delete bestSolution;

	return 0;
}



